--------------------------------------------------------------------------------
--      Copyright (c) 2015 - 2016 , 蒙占志(topameng) topameng@gmail.com
--      All rights reserved.
--      Use, modification and distribution are subject to the "MIT License"
--------------------------------------------------------------------------------
local create = coroutine.create
local running = coroutine.running
local resume = coroutine.resume
local yield = coroutine.yield
local status = coroutine.status
local error = error
local unpack = unpack
local debug = debug
local FrameTimer = FrameTimer
local CoTimer = CoTimer

coroutine.ActionCo = 0

local comap = {}
setmetatable(comap, {__mode = "kv"})
--finally
local finallyMap = {}
setmetatable(finallyMap, {__mode = "kv"})
local isCoroutineFinalizing = function(co)
	if finallyMap[co] == nil then return false end
	return status(co) == 'dead'
end
local doFinally = function(co)
	if finallyMap[co] == nil then return end
	local finally = finallyMap[co]
	xpcall(function()
		finally()
	end,function(errmsg) logError(debug.traceback(errmsg)) end)
	finallyMap[co] = nil
end
--Pool
local pool = {}

function coroutine.start(f, ...)	
	local co = create(f)
	
	if running() == nil then
		local flag, msg = resume(co, ...)

		if not flag then
			error(debug.traceback(co, msg))
		end
	else
		local args = {...}
		local timer = nil		
		
		local action = function()
			coroutine.ActionCo = coroutine.ActionCo + 1

			comap[co] = nil
			timer:Stop()
			timer.func = nil
			local flag, msg = resume(co, unpack(args))						
			table.insert(pool, timer)

			if not flag then
				error(debug.traceback(co, msg))						
			end

			if isCoroutineFinalizing(co) then doFinally(co) end
		end
			
		if #pool > 0 then
			timer = table.remove(pool)
			timer:Reset(action, 0, 1)
		else
			timer = FrameTimer.New(action, 0, 1)
		end
		
		comap[co] = timer
		timer:Start()		
	end

	return co
end

function coroutine.wait(t, co, ...)
	local args = {...}
	co = co or running()		
	local timer = nil
		
	local action = function()
		coroutine.ActionCo = coroutine.ActionCo + 1

		comap[co] = nil		
		timer.func = nil
		timer:Stop()
		local flag, msg = resume(co, unpack(args))

		if not flag then
			error(debug.traceback(co, msg))
		end

		if isCoroutineFinalizing(co) then doFinally(co) end
	end
	
	timer = CoTimer.New(action, t, 1)
	comap[co] = timer	
	timer:Start()
	return yield()
end

function coroutine.step(t, co, ...)
	local args = {...}
	co = co or running()		
	local timer = nil
	
	local action = function()
		coroutine.ActionCo = coroutine.ActionCo + 1

		comap[co] = nil
		timer:Stop()
		timer.func = nil
		local flag, msg = resume(co, unpack(args))
		table.insert(pool, timer)

		if not flag then
			error(debug.traceback(co, msg))
		end

		if isCoroutineFinalizing(co) then doFinally(co) end
	end
				
	if #pool > 0 then
		timer = table.remove(pool)
		timer:Reset(action, t or 1, 1)
	else
		timer = FrameTimer.New(action, t or 1, 1)
	end

	comap[co] = timer
	timer:Start()
	return yield()
end

function coroutine.waitUntil(func, co)
	co = co or running()
	local timer = nil

	local action = function()
		coroutine.ActionCo = coroutine.ActionCo + 1
		if func() == false then
			return
		end

		comap[co] = nil
		timer:Stop()
		timer.func = nil
		local flag, msg = resume(co)
		table.insert(pool, timer)

		if not flag then
			error(debug.traceback(co, msg))
		end
		if isCoroutineFinalizing(co) then doFinally(co) end
	end

	if #pool > 0 then
		timer = table.remove(pool)
		timer:Reset(action, 1, -1)
	else
		timer = FrameTimer.New(action, 1, -1)
	end
	comap[co] = timer
	timer:Start()
	return yield()
end

function coroutine.www(www, co)
	return coroutine.waitUntil(function() return www.isDone end, co)
end

function coroutine.waitFor(enumerator, co)
	return coroutine.waitUntil(function() return not enumerator:MoveNext() end, co)
end

function coroutine.waitCo(waitCo, co)
	return coroutine.waitUntil(function() return waitCo == nil or coroutine.status(waitCo) == 'dead' end, co)
end

function coroutine.pause(co)
	local timer = comap[co]

	if timer ~= nil then
		timer:Stop()
	end
end

function coroutine.resume(co)
	local timer = comap[co]

	if timer ~= nil then
		timer:Start()
	end
end

function coroutine.stop(co)
 	local timer = comap[co] 	 	

 	if timer ~= nil then
 		comap[co] = nil
 		timer:Stop()  		
 	end
	doFinally(co)
end

function coroutine.stopAllCoroutine()
	for co, timer in pairs(comap) do
		if timer then
			timer:Stop()
		end
		comap[co] = nil
		doFinally(co)
	end
end

function coroutine.isCoroutineRunning(co)
	return comap[co] ~= nil
end

function coroutine.setFinally(co, func)
	if status(co) == 'dead' then
		xpcall(function()
			func()
		end,function(errmsg) logError(debug.traceback(errmsg)) end)
	else
		finallyMap[co] = func
	end
end

function coroutine.resumeEarly(co)
	local timer = comap[co]
	if timer == nil then return end
	comap[co] = nil
	timer:Stop()
	local func = timer.func
	timer.func = nil
	func()
end